package com.jwong.common.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class InstanceUtils<S> {

    public static <S> S instance(Class<S> type, ClassLoader loader) {
        return instance(type, loader, null, null);
    }

    public static <S> S instance(Class<S> type, ClassLoader loader, Class[] argTypes, Object[] args) {
        return InnerInstancer.innerInstancer(type).getInstance(type, loader, argTypes, args);
    }

    private static class InnerInstancer<S> {

        private final ConcurrentMap<Class<S>, Holder<Object>> instancesMap = new ConcurrentHashMap<>();

        private S getInstance(Class<S> type, ClassLoader loader, Class[] argTypes, Object[] args) {
            Holder<Object> holder = instancesMap.get(type);
            if (holder == null) {
                instancesMap.putIfAbsent(type, new Holder<>());
                holder = instancesMap.get(type);
            }
            Object instance = holder.get();
            if (instance == null) {
                synchronized (holder) {
                    instance = holder.get();
                    if (instance == null) {
                        instance = createNewExtension(type, loader, argTypes, args);
                        holder.set(instance);
                    }
                }
            }
            return (S)instance;
        }

        private S createNewExtension(Class<S> clazz, ClassLoader loader, Class[] argTypes, Object[] args) {
            try {
                S newInstance = initInstance(clazz, argTypes, args);
                return newInstance;
            } catch (Throwable t) {
                throw new IllegalStateException("instance(class: " + clazz + ")  could not be instantiated: " + t.getMessage(), t);
            }
        }

        private S initInstance(Class<S> implClazz, Class[] argTypes, Object[] args)
                throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            S s = null;
            if (argTypes != null && args != null) {
                Constructor<S> constructor = implClazz.getDeclaredConstructor(argTypes);
                s = constructor.newInstance(args);
            } else {
                s = implClazz.newInstance();
            }
            return s;
        }

        private volatile static InnerInstancer innerInstancer;

        private InnerInstancer(){}

        private static <S> InnerInstancer<S> innerInstancer(Class<S> type) {
            if (innerInstancer == null) {
                synchronized(InnerInstancer.class) {
                    if (innerInstancer == null) {
                        innerInstancer = new InnerInstancer();
                    }
                }
            }
            return innerInstancer;
        }
    }

    private static class Holder<T> {

        private volatile T value;

        private void set(T value) {
            this.value = value;
        }

        private T get() {
            return value;
        }
    }

}
